﻿#Region "Copyright"

' Copyright 2007 Ben Reichelt - http://benreichelt.net
' 
' Licensed under the Apache License, Version 2.0 (the "License");
' you may not use this file except in compliance with the License.
' You may obtain a copy of the License at
' 
'     http://www.apache.org/licenses/LICENSE-2.0
' 
' Unless required by applicable law or agreed to in writing, software
' distributed under the License is distributed on an "AS IS" BASIS,
' WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
' See the License for the specific language governing permissions and
' limitations under the License.


#End Region

Imports System.Xml
Imports System.Xml.Linq

Public Class XmlMetaData

    Public Const DbmlNamespace As String = "http://schemas.microsoft.com/linqtosql/dbml/2007"
    Public Const BlankNamespace As String = ""

    Private _doc As XDocument
    Private _fileName As String

    Public Sub New(ByVal fileName As String)
        _doc = XDocument.Load(fileName)
        _fileName = fileName
    End Sub

    Public Sub New(ByVal doc As XDocument)
        _doc = doc
        _fileName = String.Empty
    End Sub

    ''' <summary>
    ''' Finds the table with the specified name.
    ''' </summary>
    ''' <param name="name">The name of the table to find</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function FindTable(ByVal name As String) As MetaDataTable

        Return New MetaDataTable(FindTableCore(name), Me)

    End Function

    ''' <summary>
    ''' Finds the type with the specified name.
    ''' </summary>
    ''' <param name="name">The name of the type to find</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function FindType(ByVal name As String) As MetaDataType

        Dim q = From c In _doc.Descendants(Names.TypeXName) _
                Where c.Attribute("Name") IsNot Nothing AndAlso c.Attribute("Name").Value.Equals(name, Strings.DefaultComparisonType) _
                Select c

        If q.Count = 0 Then
            Throw New TypeNotFoundException(name)
        End If

        Dim first As XElement = q.First()
        Return New MetaDataType(first, Me)

    End Function

    ''' <summary>
    ''' Finds all columns in this metadata file
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function FindAllColumns() As List(Of MetaDataColumn)

        Dim q = From c In _doc.Descendants(Names.ColumnXName) Select c
        Dim list As New List(Of MetaDataColumn)
        For Each item As XElement In q.ToList
            list.Add(New MetaDataColumn(item, Me))
        Next
        Return list

    End Function

    ''' <summary>
    ''' Finds all associations in this metadata file
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function FindAllAssociations() As List(Of MetaDataAssociation)

        Dim q = From c In _doc.Descendants(Names.AssociationXName) Select c
        Dim list As New List(Of MetaDataAssociation)
        For Each item As XElement In q.ToList
            list.Add(New MetaDataAssociation(item, Me))
        Next
        Return list

    End Function

    ''' <summary>
    ''' Finds all associations that involve the given column
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function FindAllAssociationsByColumn(ByVal column As String) As List(Of MetaDataAssociation)

        Dim q = From c In _doc.Descendants(Names.AssociationXName) _
                Where _
                (c.Attribute("OtherKey") IsNot Nothing AndAlso c.Attribute("OtherKey").Value.Equals(column, Strings.DefaultComparisonType)) _
                Or _
                (c.Attribute("ThisKey") IsNot Nothing AndAlso c.Attribute("ThisKey").Value.Equals(column, Strings.DefaultComparisonType)) _
                Select c

        Dim list As New List(Of MetaDataAssociation)
        For Each item As XElement In q.ToList
            list.Add(New MetaDataAssociation(item, Me))
        Next
        Return list

    End Function

    ''' <summary>
    ''' Finds all associations that involve the given type
    ''' </summary>
    ''' <param name="typeName"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function FindAllAssociationsByType(ByVal typeName As String) As List(Of MetaDataAssociation)
        Dim q = From c In _doc.Descendants(Names.AssociationXName) _
                Where _
                (c.Attribute("Type") IsNot Nothing AndAlso c.Attribute("Type").Value.Equals(typeName, Strings.DefaultComparisonType)) _
                Select c

        Dim list As New List(Of MetaDataAssociation)
        For Each item As XElement In q.ToList
            list.Add(New MetaDataAssociation(item, Me))
        Next
        Return list
    End Function

    ''' <summary>
    ''' Finds all types in this meta data
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function FindAllTypes() As List(Of MetaDataType)

        Dim q = From c In _doc.Descendants(Names.TypeXName) Select c
        Dim list As New List(Of MetaDataType)
        For Each item As XElement In q.ToList
            list.Add(New MetaDataType(item, Me))
        Next
        Return list

    End Function

    ''' <summary>
    ''' Finds all tables in this metadata file
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function FindAllTables() As List(Of MetaDataTable)

        Dim q = From c In _doc.Descendants(Names.TableXName) Select c
        Dim list As New List(Of MetaDataTable)
        For Each item As XElement In q.ToList
            list.Add(New MetaDataTable(item, Me))
        Next
        Return list

    End Function

    ''' <summary>
    ''' Finds all associations that use the given column
    ''' as the 'ThisKey' value for the association
    ''' </summary>
    ''' <param name="name">The value of the 'ThisKey' attribute for the association</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function FindSourceAssociationReferences(ByVal name As String) As List(Of MetaDataAssociation)

        Dim q = From c In _doc.Descendants(Names.AssociationXName) _
                Where c.Attribute("ThisKey") IsNot Nothing AndAlso c.Attribute("ThisKey").Value.Equals(name, Strings.DefaultComparisonType) _
                Select c
        Dim list As New List(Of MetaDataAssociation)
        For Each el As XElement In q.ToList
            list.Add(New MetaDataAssociation(el, Me))
        Next
        Return list
    End Function

    ''' <summary>
    ''' Finds all associations that use the given column
    ''' as the 'OtherKey' value for the association
    ''' </summary>
    ''' <param name="name">The value of the 'OtherKey' attribute for the association</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function FindDestinationAssociationReferences(ByVal name As String) As List(Of MetaDataAssociation)

        Dim q = From c In _doc.Descendants(Names.AssociationXName) _
                Where c.Attribute("OtherKey") IsNot Nothing AndAlso c.Attribute("OtherKey").Value.Equals(name, Strings.DefaultComparisonType) _
                Select c
        Dim list As New List(Of MetaDataAssociation)
        For Each el As XElement In q.ToList
            list.Add(New MetaDataAssociation(el, Me))
        Next
        Return list
    End Function

    ''' <summary>
    ''' Saves this metadata file to the filesystem
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Save()
        If (Not String.IsNullOrEmpty(_fileName)) Then
            _doc.Save(_fileName)
        End If
    End Sub

    ''' <summary>
    ''' Updates associations to use a new column name. This is used when a column gets renamed.
    ''' </summary>
    ''' <param name="previousName">The old name of the column</param>
    ''' <param name="newName">The new name of the column</param>
    ''' <remarks></remarks>
    Public Sub UpdateSourceAssociations(ByVal previousName As String, ByVal newName As String)

        If Not String.IsNullOrEmpty(previousName) Then
            Dim refs As List(Of MetaDataAssociation) = FindSourceAssociationReferences(previousName)
            For Each assoc As MetaDataAssociation In refs
                assoc.ChangeThisKey(newName)
            Next
        End If

    End Sub

    ''' <summary>
    ''' Updates associations to use a new column name. This is used when a column gets renamed.
    ''' </summary>
    ''' <param name="previousName">The old name of the column</param>
    ''' <param name="newName">The new name of the column</param>
    ''' <remarks></remarks>
    Public Sub UpdateDestinationAssociations(ByVal previousName As String, ByVal newName As String)

        If Not String.IsNullOrEmpty(previousName) Then
            Dim refs As List(Of MetaDataAssociation) = FindDestinationAssociationReferences(previousName)
            For Each assoc As MetaDataAssociation In refs
                assoc.ChangeOtherKey(newName)
            Next
        End If

    End Sub

    ''' <summary>
    ''' Removes a table from this metadata and all references to its Type(s)
    ''' </summary>
    ''' <param name="name">The name of the table to remove</param>
    ''' <remarks></remarks>
    Public Sub RemoveTable(ByVal name As String)

        ' get the table to remove
        Dim tbl As MetaDataTable = FindTable(name)

        ' for each type that this table contains,
        ' remove any association that they type is used in
        ' because that association will no longer be valid
        For Each type As MetaDataType In tbl.Types
            For Each assoc As MetaDataAssociation In FindAllAssociationsByType(type.MemberName)
                RemoveAssociation(assoc.MemberName)
            Next
        Next

        ' remove the table
        Dim el As XElement = FindTableCore(name)
        el.Remove()

    End Sub

    ''' <summary>
    ''' Removes an association by name
    ''' </summary>
    ''' <param name="name"></param>
    ''' <remarks></remarks>
    Public Sub RemoveAssociation(ByVal name As String)
        Dim el As XElement = FindAssociationCore(name)
        el.Remove()
    End Sub

    ''' <summary>
    ''' Finds a table's XElement by name
    ''' </summary>
    ''' <param name="name">The name of the table to find</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function FindTableCore(ByVal name As String) As XElement

        If String.IsNullOrEmpty(name) Then
            Throw New ArgumentNullException("name")
        End If

        Dim q = From c In _doc.Descendants(Names.TableXName) _
                Where c.Attribute("Name") IsNot Nothing AndAlso c.Attribute("Name").Value.Equals(name, Strings.DefaultComparisonType) _
                Select c

        If q.Count = 0 Then
            Throw New TableNotFoundException(name)
        End If

        Return q.First()

    End Function


    ''' <summary>
    ''' Finds an association's XElement by name
    ''' </summary>
    ''' <param name="name"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function FindAssociationCore(ByVal name As String) As XElement

        If String.IsNullOrEmpty(name) Then
            Throw New ArgumentNullException("name")
        End If

        Dim q = From c In _doc.Descendants(Names.AssociationXName) _
                Where _
                (c.Attribute("Name") IsNot Nothing AndAlso c.Attribute("Name").Value.Equals(name, Strings.DefaultComparisonType)) _
                Select c

        If q.Count = 0 Then
            Throw New AssociationNotFoundException(name)
        End If

        Return q.First

    End Function

End Class

